<?php
namespace App\Services\Activity;

use App\Enums\CarnivalEnum;
use App\Enums\RedisKeyEnum;
use App\Exceptions\BasicException;
use App\Models\ActivityDB\Carnival2022Match;
use App\Models\ActivityDB\Carnival2022RewardLog;
use App\Models\ActivityDB\Carnival2022RewardSetting;
use App\Models\ActivityDB\Carnival2022RoundSetting;
use App\Models\ActivityDB\Carnival2022Setting;
use App\Models\MainDB\Gift;
use App\Models\MainDB\GiftBackpack;
use App\Models\MainDB\MallOrders;
use App\Models\MainDB\MallProducts;
use App\Models\MainDB\UserData;
use App\Models\MainDB\UserRoom;
use App\Repositories\Activity\CarnivalRepository;
use Illuminate\Support\Facades\DB;
use Throwable as ThrowableAlias;

class CarnivalRewardService
{
    private $roomId;
    private $userDatas;
    /**
     * @var Carnival2022Setting
     */
    private $setting;
    /**
     * @var Carnival2022RoundSetting
     */
    private $roundSetting;
    private $rewardLogType;
    private $rank_sort;

    public function normal(int $settingId, $round = 0)
    {
        $query = Carnival2022RewardSetting::query();
        $query->where('is_visible', 1)
              ->where('setting_id', $settingId)
              ->where('count', '>', 0)
              ->where('type', $this->rewardLogType);
        return $query->get();
    }

    /**
     * @throws BasicException
     */
    private function verifySetting($settingId)
    {
        $date = date('Y-m-d H:i:s');
        $setting = Carnival2022Setting::query()->find($settingId);
        if (empty($setting)) {
            throw new BasicException(52012, '活动不存在');
        }

        /**
         * @var $setting Carnival2022Setting
         */
        if ($setting->end_at > $date) {
            throw new BasicException(52001);
        }
        if ($date > $setting->receive_at) {
            throw new BasicException(52002);
        }
        return $setting;
    }


    /**
     * @throws BasicException
     */
    public function charmBeforeReceive(array $userIds, int $userId, int $settingId): void
    {
        $setting = $this->verifySetting($settingId);
        $userDataList = UserData::query()->whereIn('user_id', $userIds)->pluck(  'user_id', 'user_code');
        if ($userDataList->isEmpty()) {
            throw new BasicException(52003);
        }
        $matchModel = new Carnival2022Match();
        $roundModel = new Carnival2022RoundSetting();
        $roundInfo = $roundModel->getOneLast($settingId);
        if (empty($roundInfo)) {
            throw new BasicException(52012);
        }

        /**
         * @var $roundInfo Carnival2022RoundSetting
         */
        //可以领取奖励的match 只取进入决赛的两位
        $matchList = $matchModel->getWinners($settingId);
        $userRoom = UserRoom::query()->where('user_id', $userId)->first();


        if (empty($userRoom)) {
            throw new BasicException(52012);
        }
        $rank_sort = array_search($userRoom->id, array_column($matchList->toArray(), 'room_id'));
        if ($rank_sort !== false) {
            $rank_sort += 1;
        }
        $matchInfo = $matchList->where('room_id', $userRoom->id)->first();
        if (empty($matchInfo)) {
            throw new BasicException(52004);
        }
        $this->roomId = $matchInfo->room_id;
        $this->setting = $setting;
        $this->roundSetting = $roundInfo;
        $this->userDatas = $userDataList->toArray();
        $this->rank_sort = $rank_sort;
    }


    /**
     * @throws BasicException
     */
    private function verifyMatch($match): void
    {
        if (empty($match)) {
            throw new BasicException(52005);
        }
        if ($match->is_finish) {
            throw new BasicException(52006);
        }

        if ($match->give_num + count($this->userDatas) > $this->setting->give_num) {
            $msg = sprintf("分配超过上限,你最多还能分配%d", $this->setting->give_num - $match->give_num);
            throw new BasicException(52007, $msg);
        }
    }

    /**
     * @throws BasicException
     */
    private function verifyRewardLog(int $settingId): void
    {
        //用户重复领取
        $rewardLogInfo = Carnival2022RewardLog::query()
            ->where('setting_id', $settingId)
            ->where('room_id', $this->roomId)
            ->whereIn('user_id', $this->userDatas)
            ->first();

        if (!empty($rewardLogInfo)) {
            $userDatas = array_flip($this->userDatas);
            $msg = sprintf('该用户%s已分配过奖励', $userDatas[$rewardLogInfo->user_id ?? '']);
            throw new BasicException(50005, $msg);
        }
    }


    /**
     * @throws BasicException
     */
    public function charmReceive(int $settingId)
    {
        try {
            $this->rewardLogType = CarnivalEnum::LOG_TYPE_CHARM;
            $rewardLog = [];

            $db = DB::connection('mysql');
            $activityDb = DB::connection('mysql_activity');
            $db->beginTransaction();
            $activityDb->beginTransaction();

            $match = Carnival2022Match::query()->where('setting_id', $settingId)->where('room_id', $this->roomId)->lockForUpdate()->first();
            $this->verifyMatch($match);
            $this->verifyRewardLog($settingId);

            /**
             * @var $match Carnival2022Match
             */
            $currRound = $match->promotion_round;
            $roundInfo = Carnival2022RoundSetting::query()->where('setting_id', $settingId)->where('round', $currRound)->first();

            if (empty($roundInfo)) {
                throw new BasicException(52012);
            }

            $roundId = $roundInfo->id;
            $rewards = $this->normal($settingId);

            if ($rewards->isNotEmpty()) {


                foreach ($this->userDatas as $uid) {
                    $rewardLog[] = $this->addGiftProduct($rewards, $uid, $roundId, $settingId, $this->rank_sort);
                }

                if ($rewardLog) {
                    $rewardLog = array_merge(...$rewardLog);

                    $extra = [];
                    if ($match->give_num+count($this->userDatas) >= $this->setting->give_num) {
                        $extra['is_finish'] = 1;
                    }

                    $afterRows = Carnival2022Match::query()
                        ->where('id', $match->id)
                        ->where('is_finish', 0)
                        ->where('give_num', $match->give_num)
                        ->increment('give_num', count($this->userDatas), $extra);

                    if ($afterRows !== 1) {
                        throw new \RuntimeException('更新领奖进度失败');
                    }
                    Carnival2022RewardLog::query()->insert($rewardLog);
                }

            }

            $db->commit();
            $activityDb->commit();

        } catch (ThrowableAlias $exception) {
            isset($db) && $db->rollBack();
            isset($activityDb) && $activityDb->rollBack();
            $code = $exception instanceof BasicException ? $exception->getCode() : 52013;
            $msg = $exception instanceof BasicException ? $exception->getMessage() : '';
            throw new BasicException($code, $msg);
        }
        return $rewards;

    }

    private function addGiftProduct($rewards, int $uid, int $roundId, int $settingId, $rank_sort = 0)
    {
        // 礼物
        $rewards->where('reward_type', Gift::RELATION_TYPE)->each(function ($item) use ($uid, $roundId, &$rewardLog, $settingId, $rank_sort) {

            $item->reward = $item->gift;
            if (! empty($item->reward) && $rank_sort) {
                if ($item->count_json) {
                    $count_json = json_decode($item->count_json, true);
                    $count = $count_json[$rank_sort] ?: 0;
                    //魅力榜第一名 送的天数 要加上第二名的天数
                    if ($this->rewardLogType == CarnivalEnum::LOG_TYPE_CHARM) {
                        $count += $count_json[$rank_sort + 1];
                    }
                } else {
                    $count = $item->count;
                }
                if ($count > 0) {
                    (new GiftBackpack())->addGift($uid, $item->reward->id, $count);
                    $rewardLog[] = $this->makeRewardLogData($item, $uid, $roundId, $settingId);
                }
            }

        });

        // 商品
        $rewards->where('reward_type', MallProducts::RELATION_TYPE)->each(function ($item) use ($uid, $roundId, &$rewardLog, $settingId, $rank_sort) {
            $item->reward = $item->product;
            if (! empty($item->reward) && $rank_sort) {
                if ($item->count_json) {
                    $count_json = json_decode($item->count_json, true);
                    $count = $count_json[$rank_sort] ?: 0;
                    //魅力榜第一名 送的天数 要加上第二名的天数
                    if ($this->rewardLogType == CarnivalEnum::LOG_TYPE_CHARM) {
                        $count += $count_json[$rank_sort + 1];
                    }
                } else {
                    $count = $item->count;
                }
                if ($count > 0) {
                    (new MallOrders())->addProduct($uid, $item->reward->id, $count, $item->reward->category_id);
                    $rewardLog[] = $this->makeRewardLogData($item, $uid, $roundId, $settingId);
                }
            }
        });
        return $rewardLog;
    }


    private function makeRewardLogData($item, int $userId, int $roundId, int $settingId): array
    {
        $totalCoin = MallProducts::RELATION_TYPE === $item->reward_type ? 0 : $item->count * $item->reward->price;
        return [
            'setting_id'  => $settingId,
            'room_id'     => $this->roomId ?? 0,
            'round_id'    => $roundId,
            'type'        => $this->rewardLogType,
            'user_id'     => $userId,
            'reward_type' => $item->reward_type,
            'reward_id'   => $item->reward_id,
            'count'       => $item->count,
            'total_coin'  => $totalCoin,
            'desc'        => ''
        ];
    }


    /**
     * @throws BasicException
     */
    public function championReceive(int $userId, $settingId): void
    {
        $this->verifySetting($settingId);
        $repo = new CarnivalRepository();
        $result = $repo->champion($userId, $settingId);
        if (empty($result)) {
            throw new BasicException(52009);
        }
        $rank = $result->forPage(1, CarnivalEnum::REWARD_NUM_CHAMPION)->toArray();
        $user_ids = array_column($rank, 'gift_user');
        if (!in_array($userId, $user_ids)) {
            throw new BasicException(52010);
        }
        $rank_sort = array_search($userId, $user_ids);
        if ($rank_sort !== false) {
            $rank_sort += 1;
        }
        $this->rank_sort = $rank_sort;
        $this->rewardLogType = CarnivalEnum::LOG_TYPE_CHAMPION;
        $this->userReward($userId, $settingId);
    }

    //财富榜领奖

    /**
     * @throws BasicException
     */
    public function wealthReceive(int $userId, $settingId): void
    {
        $this->verifySetting($settingId);
        $repo = new CarnivalRepository();
        $result = $repo->wealthRank($userId, $settingId);

        if ($result->isEmpty() || $result->count() === 1) {
            throw new BasicException(52009);
        }

        $rank = $result->forPage(1, CarnivalEnum::REWARD_NUM_WEALTH)->toArray();
        $user_ids = array_column($rank, 'user_id');
        if (!in_array($userId, $user_ids)) {
            throw new BasicException(52010);
        }
        $rank_sort = array_search($userId, $user_ids);
        if ($rank_sort !== false) {
            $rank_sort += 1;
        }
        $this->rank_sort = $rank_sort;
        $this->rewardLogType = CarnivalEnum::LOG_TYPE_WEALTH;
        $this->userReward($userId, $settingId);
    }


    //一个用户领取奖励

    /**
     * @throws BasicException
     */
    private function userReward($userId, $settingId): void
    {
        try {

            $key = sprintf(RedisKeyEnum::CARNIVAL_USER_RECEIVE_LOCK, $this->rewardLogType, $userId);
            if (!app('redis')->set($key, 'lock', 'ex', 15, 'nx')) {
                throw new BasicException(52011);
            }

            $db = DB::connection('mysql');
            $activityDb = DB::connection('mysql_activity');
            $db->beginTransaction();
            $activityDb->beginTransaction();

            $rewardLogInfo = Carnival2022RewardLog::query()
                ->where('type', $this->rewardLogType)
                ->where('user_id', $userId)
                ->where('setting_id', $settingId)
                ->lockForUpdate()
                ->first();

            if ($rewardLogInfo) {
                throw new BasicException(52006);
            }

            $rewards = $this->normal($settingId);

            if ($rewards->isNotEmpty()) {
                $rewardLog = $this->addGiftProduct($rewards, $userId, 0, $settingId, $this->rank_sort);
                if ($rewardLog) {
                    Carnival2022RewardLog::query()->insert($rewardLog);
                }
            }

            $db->commit();
            $activityDb->commit();

        } catch (ThrowableAlias $exception) {
            isset($db) && $db->rollBack();
            isset($activityDb) && $activityDb->rollBack();
            $code = $exception instanceof BasicException ? $exception->getCode() : 52013;
            $msg  = $exception instanceof BasicException ? $exception->getMessage() : '';
            throw new BasicException($code, $msg);
        }
    }




}
